#pragma once

#include "../Render/Tex2D.h"
#include "../Render/PBO.h"
#include "../Render/Quad.h"
#include "../Render/Shader.h"
#include "../Render/Uniform.h"

#include <glm/glm.hpp>
#include <GL3/gl3w.h>

#include <string>
#include <vector>

namespace Views
{
	struct ViewBuffer
	{
		std::vector<unsigned int> characters_dirty; bool dirty_character;
		std::vector<unsigned int> background_dirty; bool dirty_background;
		std::vector<unsigned int> foreground_dirty; bool dirty_foreground;

		Render::Tex2DPtr tex_characters;
		Render::Tex2DPtr tex_background;
		Render::Tex2DPtr tex_foreground;

		Render::PBOUPtr pbo_characters;
		Render::PBOUPtr pbo_background;
		Render::PBOUPtr pbo_foreground;

		Render::UniformPtr uni_characters;
		Render::UniformPtr uni_background;
		Render::UniformPtr uni_foreground;

		ViewBuffer(const glm::ivec2 &viewport) 
		{
			characters_dirty.resize(viewport.x*viewport.y, 0); dirty_character = false;
			background_dirty.resize(viewport.x*viewport.y, 0); dirty_background = false;
			foreground_dirty.resize(viewport.x*viewport.y, 0); dirty_foreground = false;

			std::vector<unsigned char> temp_data;
			temp_data.resize(4*viewport.x*viewport.y, 0);
			Render::T2DTexParams tex_params(GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, 4, viewport.x, viewport.y, GL_REPEAT, temp_data);
			tex_characters = std::make_shared<Render::Tex2D>(tex_params);
			tex_background = std::make_shared<Render::Tex2D>(tex_params);
			memset(&temp_data[0], 255, temp_data.size());
			tex_foreground = std::make_shared<Render::Tex2D>(tex_params);
			Render::Tex2D::unbindTex2D();

			pbo_characters = std::unique_ptr<Render::PBO>(new Render::PBO(temp_data.size(), GL_STREAM_DRAW, true)); pbo_characters->unbind();
			pbo_background = std::unique_ptr<Render::PBO>(new Render::PBO(temp_data.size(), GL_STREAM_DRAW, true)); pbo_background->unbind();
			pbo_foreground = std::unique_ptr<Render::PBO>(new Render::PBO(temp_data.size(), GL_STREAM_DRAW, true)); pbo_foreground->unbind();

			uni_characters = std::make_shared<Render::Uniform>("Characters");
			uni_foreground = std::make_shared<Render::Uniform>("Foreground");
			uni_background = std::make_shared<Render::Uniform>("Background");
		}

		bool hasDirtyCharacters() 
		{ 
			return dirty_character;
		}
		bool hasDirtyBackground() 
		{ 
			return dirty_background;
		}
		bool hasDirtyForeground()
		{ 
			return dirty_foreground;
		}
		void clearDirty()
		{
			std::swap(characters_dirty, std::vector<unsigned int>());
			std::swap(background_dirty, std::vector<unsigned int>());
			std::swap(foreground_dirty, std::vector<unsigned int>());
			dirty_character = false;
			dirty_background = false;
			dirty_foreground = false;
		}

		unsigned int bind(unsigned int active_shader_id, unsigned int active_tex_offset)
		{
			glActiveTexture(GL_TEXTURE0+active_tex_offset+0);
			tex_background->bind();
			uni_background->bind((int)0, active_shader_id);
			glActiveTexture(GL_TEXTURE0+active_tex_offset+1);
			tex_foreground->bind();
			uni_background->bind((int)1, active_shader_id);
			glActiveTexture(GL_TEXTURE0+active_tex_offset+2);
			tex_characters->bind();
			uni_background->bind((int)2, active_shader_id);
			return active_tex_offset + 3;
		}
	};
	typedef std::shared_ptr<ViewBuffer> ViewBufferPtr;

	class ViewContent;
	class IView
	{
	public:
		IView(const std::string &id, const glm::ivec2 &viewport) : id(id), viewport(viewport) { quad = std::unique_ptr<Render::Quad>(new Render::Quad()); }
		virtual ~IView() {}

		virtual void update(float /*dt*/) = 0;
		virtual void render() = 0;

		virtual bool keyInput(int key, int pressed) = 0;
		virtual bool mouseButtonInput(int button, int pressed) = 0;
		virtual bool mouseMoveInput(int x, int y) = 0;

		const std::string &getId() const { return id; }

		ViewBufferPtr getLayer(unsigned int index) { if(!layers.empty() && index < layers.size()) return layers[index]; else return nullptr; }

		void setDefaultBackgroundColor(const glm::ivec4 &color, unsigned int layer_index)
		{
			auto layer = getLayer(layer_index);
			if(layer == nullptr)
				return;

			layer->dirty_background = true;

			for(unsigned int i = 0; i < layer->background_dirty.size(); i++)
			{
				layer->tex_background->getData()[i*4 + 0] = color.r;
				layer->tex_background->getData()[i*4 + 1] = color.g;
				layer->tex_background->getData()[i*4 + 2] = color.b;
				layer->tex_background->getData()[i*4 + 3] = color.a;
				layer->background_dirty[i] = true;
			}
			//memset(&layer->background_dirty[0], 1, layer->background_dirty.size()*sizeof(bool));
		}

		void setDefaultForegroundColor(const glm::vec4 &color, unsigned int layer_index)
		{
			auto layer = getLayer(layer_index);
			if(layer == nullptr)
				return;

			layer->dirty_foreground = true;

			for(unsigned int i = 0; i < layer->foreground_dirty.size(); i++)
			{
				layer->tex_foreground->getData()[i*4 + 0] = color.r;
				layer->tex_foreground->getData()[i*4 + 1] = color.g;
				layer->tex_foreground->getData()[i*4 + 2] = color.b;
				layer->tex_foreground->getData()[i*4 + 3] = color.a;
				layer->foreground_dirty[i] = true;
			}
		}

	protected:
		std::string id;
		std::vector<ViewBufferPtr> layers;
		std::unique_ptr<Render::Quad> quad;
		Render::ShaderPtr shader;

		Render::Tex2DPtr tex_font;
		Render::UniformPtr uni_font;

		glm::vec2 fontsize;
		Render::UniformPtr uni_fontsize;

		glm::ivec2 viewport;
		Render::UniformPtr uni_viewport;

		glm::vec2 viewport_coef;
		Render::UniformPtr uni_viewport_coef;
	};

}
